﻿Option Explicit On
Option Strict On

Imports Microsoft.Office.Interop
Imports System.DirectoryServices
Imports System.IO
Imports System.Text
Imports System.Security.Principal
Imports TfsProjectTools


Public Class ProvisionFormCP
   Private Const TFS_WEB_URI_PART As String = "web/Index.aspx?pname="

   Private myProjects As New List(Of TFSProjectAttribs)
   Private myTfsServer As New Server(My.Settings.TfsServerUri)
   Private WithEvents myProvisioner As New ProjectProvisioner()


   Private Sub ProvisionFormCP_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      txtServer.Text = myTfsServer.Uri.AbsoluteUri
      txtReaders.Text = My.Settings.DefaultReaderGroupAccountNames

      With My.Application.Info.Version
         lblBuild.Text = "v" & .Major.ToString & "." & .Minor.ToString & "." & .Build.ToString
      End With
   End Sub

   Private Sub txtServer_TextChanged(sender As System.Object, e As System.EventArgs) Handles txtServer.TextChanged
      myTfsServer = New Server(txtServer.Text.Trim())

      With cboCollection
         .Sorted = True
         .SelectedIndex = -1
         .Items.Clear()

         For Each collectionName As String In myTfsServer.GetCollectionNames()
            .Items.Add(collectionName)
         Next

         If .Items.Count > 0 Then
            .Enabled = True
            .SelectedIndex = 0
         Else
            .Enabled = False
         End If

         butQueue.Enabled = .Enabled
      End With
   End Sub

   Private Sub cboCollection_SelectedIndexChanged(sender As Object, e As System.EventArgs) Handles cboCollection.SelectedIndexChanged
      With cboTemplate
         .Sorted = True
         .SelectedIndex = -1
         .Items.Clear()

         If cboCollection.SelectedIndex >= 0 Then
            For Each templateName As String In myTfsServer.GetProcessTemplateNames(cboCollection.Text)
               .Items.Add(templateName)
            Next
         End If

         If .Items.Count > 0 Then
            .Enabled = True
            .SelectedIndex = 0
         Else
            .Enabled = False
         End If
      End With
   End Sub

   Private Sub butQueue_Click(sender As System.Object, e As System.EventArgs) Handles butQueue.Click
      Try
         Dim project As New TFSProjectAttribs(New Uri(myTfsServer.Uri.AbsoluteUri & "/" & cboCollection.Text), txtName.Text.Trim(), cboTemplate.Text, txtAdmin.Text.Trim())
         myProjects.Add(project)

         If myProjects.Count = 1 Then
            ShowStatus(New String("-"c, 30))
         End If
         ShowStatus("[Queued] " & project.CollectionUri.AbsoluteUri & ": " & project.ProjectName)

      Catch ex As Exception
         ShowStatus("[Error] " & ex.Message)

      End Try
   End Sub

   Private Sub butDequeue_Click(sender As System.Object, e As System.EventArgs) Handles butDequeue.Click
      With myProjects
         If .Count > 0 Then
            .RemoveAt(.Count - 1)
         End If

         If .Count > 0 Then
            ShowStatus(New String("-"c, 30))

            For n As Integer = 0 To .Count - 1
               ShowStatus("[Queued] " & .Item(n).CollectionUri.AbsoluteUri & ": " & .Item(n).ProjectName)
            Next
         End If
      End With
   End Sub

   Private Sub butProcess_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butProcess.Click
      Try
         butProcess.Enabled = False
         Cursor = Windows.Forms.Cursors.WaitCursor

         ShowStatus(New String("-"c, 30))
         ShowStatus("[Process queue] Project count: " & myProjects.Count.ToString)

         For Each project In myProjects
            Dim adminAccounts As New List(Of NTAccount)
            Dim readerAccounts As New List(Of NTAccount)

            With project
               adminAccounts.Add(New NTAccount(My.Settings.ActiveDirectoryDomainName, .ProjectAdminAccountName))

               If Not String.IsNullOrWhiteSpace(My.Settings.ProjectReaderGroupName) Then
                  Dim sep() As Char = {","c, " "c, ";"c}
                  Dim names() As String = txtReaders.Text.Split(sep, StringSplitOptions.RemoveEmptyEntries)
                  For Each name As String In names
                     readerAccounts.Add(New NTAccount(My.Settings.ActiveDirectoryDomainName, name))
                  Next
               End If

               myProvisioner.Create(.CollectionUri, .ProjectName, .ProcessTemplateName, My.Settings.ProjectAdminGroupName, adminAccounts.ToArray)
               myProvisioner.SetGroupMembership(.CollectionUri, .ProjectName, My.Settings.ProjectReaderGroupName, readerAccounts.ToArray)
            End With

            DoPostCreationProcessing(project)
         Next

         myProjects.Clear()

      Catch ex As Exception
         ShowStatus("[Error] " & ex.Message)

      Finally
         butProcess.Enabled = True
         Cursor = Windows.Forms.Cursors.Default

      End Try
   End Sub

   Private Sub myProvisioner_ProvisionProgress(progress As TfsProjectTools.ProjectProvisioner.ProvisionProgressEventType, projectName As String, message As String) Handles myProvisioner.ProvisionProgress
      Dim statusHeader As String = "[" & [Enum].GetName(progress.GetType, progress) & "] "

      Select Case progress
         Case ProjectProvisioner.ProvisionProgressEventType.BeginProject
            ShowStatus(New String("-"c, 30))
            ShowStatus(statusHeader & projectName)

         Case ProjectProvisioner.ProvisionProgressEventType.ConnectToTfsServer
            ShowStatus(New String("-"c, 30))
            ShowStatus(statusHeader & message)

         Case ProjectProvisioner.ProvisionProgressEventType.ConnectToTfsClient,
              ProjectProvisioner.ProvisionProgressEventType.CreateProjectDefinition,
              ProjectProvisioner.ProvisionProgressEventType.CreateTeamProject,
              ProjectProvisioner.ProvisionProgressEventType.RemoveGroupMember,
              ProjectProvisioner.ProvisionProgressEventType.AddGroupMember
            ShowStatus(statusHeader & message)

         Case Else
      End Select
   End Sub

   Private Sub myProvisioner_ProvisionException(projectName As String, ex As System.Exception) Handles myProvisioner.ProvisionException
      ShowStatus("[Error] " & projectName & Environment.NewLine & ex.Message)

      If ex.InnerException IsNot Nothing Then
         ShowStatus(ex.InnerException.Message)
         ShowStatus(ex.InnerException.StackTrace)
      End If

      Const findToken As String = "log:"
      Dim pos As Integer = ex.Message.IndexOf(findToken)
      If pos >= 0 Then
         Dim logSpec As String = ex.Message.Substring(pos + findToken.Length)
         If File.Exists(logSpec) Then
            ShowStatus(File.ReadAllText(logSpec))
         End If
      End If
   End Sub

   Private Sub DoPostCreationProcessing(provisionedProject As TFSProjectAttribs)
      'add web Uri
      With provisionedProject
         Dim collUri As String = .CollectionUri.AbsoluteUri
         Dim pos As Integer = collUri.LastIndexOf("/")
         .WebAccessUri = Uri.EscapeUriString(collUri.Substring(0, pos + 1) & TFS_WEB_URI_PART & .ProjectName)
      End With

      Dim ccAddress As String = String.Empty
      Dim sep() As Char = {","c, " "c, ";"c}
      Dim names() As String = My.Settings.CreateNotifyAccountNames.Split(sep, StringSplitOptions.RemoveEmptyEntries)
      For Each name As String In names
         Dim dummy As String = String.Empty
         Dim email As String = GetEmailAddressFromAccountName(name, dummy)

         If Not String.IsNullOrWhiteSpace(email) Then
            ccAddress &= email & "; "
         End If
      Next

      If chkEmail.Checked Then
         SendEmailConfirmation(provisionedProject, ccAddress)
      End If
   End Sub

   Private Sub ShowStatus(ByVal text As String)
      With txtResult
         .AppendText(Environment.NewLine & text)
         .SelectionStart = .TextLength
         .SelectionLength = 0
      End With
   End Sub

   Private Sub SendEmailConfirmation(ByVal tfsProject As TFSProjectAttribs, ccAddress As String)
      Dim toName As String = String.Empty
      Dim toAddress As String = String.Empty

      Dim sb As New StringBuilder()
      With sb
         toAddress = GetEmailAddressFromAccountName(tfsProject.ProjectAdminAccountName, toName)

         If String.IsNullOrWhiteSpace(toAddress) Then
            .AppendLine("***")
            .AppendLine("Email address for " & tfsProject.ProjectAdminAccountName & " was not found; please forward this notification to the requestor...")
            .AppendLine("***")
            .AppendLine()
         End If
         .AppendLine("Your new TFS project has been created.")
         .AppendLine()

         Dim collUri As String = tfsProject.CollectionUri.AbsoluteUri
         Dim pos As Integer = collUri.LastIndexOf("/")
         .Append("TFS project name: ")
         .Append(tfsProject.ProjectName)
         .AppendLine(" (" & collUri.Substring(pos + 1) & ")")
         .AppendLine()

         .Append("Administrator: ")
         .Append(tfsProject.ProjectAdminAccountName)
         If toName.Length > 0 Then
            .AppendLine("(" & toName & ")")
         Else
            .AppendLine()
         End If
         If tfsProject.WebAccessUri.Length > 0 Then
            .AppendLine()
            .AppendLine("Web location:")
            .AppendLine(tfsProject.WebAccessUri)
         End If
         .AppendLine()
         .AppendLine()
         .AppendLine()
         .AppendLine("Your Friendly Neighborhood TFS Administrator")
      End With
      Dim text As String = sb.ToString

      CreateOutboxMail(toAddress, ccAddress, "New TFS Project: " & tfsProject.ProjectName, text)
      ShowStatus("[Send email confirmation] " & toAddress & " (" & toName & ")")
   End Sub

   Private Sub CreateOutboxMail(ByVal [to] As String, ByVal cc As String, ByVal subject As String, ByVal body As String)
      Dim filenames(-1) As String
      CreateOutboxMail([to], cc, subject, body, filenames)
   End Sub

   Private Sub CreateOutboxMail(ByVal [to] As String, ByVal cc As String, ByVal subject As String, ByVal body As String, ByVal filenames() As String)
      Dim olApp As Outlook.Application = New Outlook.Application()
      Dim sentFolder As Outlook.MAPIFolder = olApp.GetNamespace("Mapi").GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail)
      Dim mi As Outlook.MailItem = CType(olApp.CreateItem(Outlook.OlItemType.olMailItem), Outlook.MailItem)
      Dim insp As Outlook.Inspector = mi.GetInspector

      With mi
         .To = [to]
         .CC = cc
         .Subject = subject
         .Body = body
         For Each fn As String In filenames
            .Attachments.Add(fn, Outlook.OlAttachmentType.olByValue, 1, Path.GetFileName(fn))
         Next

         .Display()
         .SaveSentMessageFolder = sentFolder
         .Send()
      End With
   End Sub

   Private Function GetEmailAddressFromAccountName(ByVal accountName As String, ByRef displayName As String) As String
      Dim addr As String = String.Empty
      Dim name As String = String.Empty

      Dim adEntry As DirectoryEntry = GetActiveDirectoryUserEntry(accountName, My.Settings.ActiveDirectoryPathUsers)

      If adEntry IsNot Nothing Then
         'spin thru all the properties
         For Each prop As PropertyValueCollection In adEntry.Properties
            Select Case prop.PropertyName
               Case "mail"
                  addr = prop.Value.ToString
               Case "displayName"
                  name = prop.Value.ToString
            End Select
         Next
      End If

      displayName = name
      Return addr
   End Function

   Private Function GetActiveDirectoryUserEntry(ByVal accountName As String, ByVal ouPath As String) As DirectoryEntry
      'search the Directory for the first entry that matches
      Dim entry As New DirectoryEntry("LDAP://" & ouPath & "," & My.Settings.ActiveDirectoryDomainContainer)
      Dim searcher As New DirectorySearcher(entry)
      With searcher
         .SearchScope = SearchScope.Subtree
         .Filter = "(" & My.Settings.ActiveDirectoryAccountPropertyName & "=" & accountName & ")"
      End With

      Dim result As SearchResult = Nothing
      Try
         result = searcher.FindOne()

      Catch ex As Exception
      End Try

      If result Is Nothing Then
         Return Nothing
      Else
         Return result.GetDirectoryEntry()
      End If
   End Function
End Class